package cn.coufran.springboot.starter.log;

import cn.coufran.springboot.starter.log.logger.Logger;
import cn.coufran.springboot.starter.log.serializer.Serializer;
import cn.coufran.springboot.starter.log.serializer.SerializerFactory;
import jakarta.annotation.Resource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.event.Level;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 日志切面
 * @author Coufran
 * @version 1.1.0
 * @since 1.0.0
 */
@Component
@Aspect
public class AutoLogAdvice {
    /** 日志记录器 */
    private Logger logger;
    /** 序列化器工厂 */
    private SerializerFactory serializerFactory;
    /** 业务异常类 */
    private Collection<Class<? extends RuntimeException>> serviceExceptionClasses = new ArrayList<>();

    /**
     * 设置日志记录器
     * @param logger 日志记录器
     */
    @Resource
    public void setLogger(Logger logger) {
        this.logger = logger;
    }

    /**
     * 设置序列化器工厂
     * @param serializerFactory 序列化器工厂
     */
    @Resource
    public void setSerializerFactory(SerializerFactory serializerFactory) {
        this.serializerFactory = serializerFactory;
    }

    /**
     * 设置业务异常类
     * @param serviceExceptionClasses 业务异常类
     */
    @Autowired(required = false)
    public void setServiceExceptionClasses(Collection<Class<? extends RuntimeException>> serviceExceptionClasses) {
        this.serviceExceptionClasses.addAll(serviceExceptionClasses);
    }

    /**
     * 添加业务异常类
     * @param serviceExceptionClasses 业务异常类
     */
    public void addServiceExceptionClass(Class<? extends RuntimeException>... serviceExceptionClasses) {
        this.serviceExceptionClasses.addAll(Arrays.asList(serviceExceptionClasses));
    }

    /**
     * 切面逻辑
     * @param pjp pjp
     * @return result
     * @throws Throwable error
     */
    @Around("@annotation(cn.coufran.springboot.starter.log.AutoLog)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        AutoLog resultAutoLog = null; // 默认结果标记
        List<AutoLog> parametersAutoLog = null; // 默认参数标记

        // 获取注解标记
        Signature signature = pjp.getSignature();
        if (signature instanceof MethodSignature) {
            MethodSignature methodSignature = (MethodSignature) signature;
            final AutoLog methodAutoLog = methodSignature.getMethod().getAnnotation(AutoLog.class);
            resultAutoLog = methodAutoLog;
            Parameter[] parameters = methodSignature.getMethod().getParameters();
            parametersAutoLog = Arrays.stream(parameters)
                    .map(parameter -> parameter.getAnnotation(AutoLog.class))
                    .map(autoLog -> autoLog != null ? autoLog : methodAutoLog)
                    .collect(Collectors.toList());
        }

        // 打印参数
        Level logLevel = resultAutoLog.level();
        String interfaceId = null;
        try {
            if (logger.isLogEnabled(logLevel)) {
                Object[] args = pjp.getArgs();
                for (int i=0; i<args.length; i++) {
                    AutoLog parameterAutoLog = parametersAutoLog.get(i);
                    args[i] = this.toString(parameterAutoLog, args[i]);
                }

                interfaceId = this.getInterfaceId(interfaceId, pjp);
                logger.log(logLevel, "{}.param: {}", interfaceId, Arrays.toString(args));
            }
        } catch (Exception e) {
            logger.error("AutoLog error", e);
        }

        // 执行方法，打印异常
        Object result;
        try {
            result = pjp.proceed();
        } catch (Throwable e) {
            if (this.isServiceException(e)) { // 业务错误
                if (logger.isLogEnabled(logLevel)) {
                    interfaceId = this.getInterfaceId(interfaceId, pjp);
                    logger.log(logLevel, "{}.error: {}", interfaceId, e.getMessage());
                }
            } else {
                if (logger.isErrorEnabled()) {
                    interfaceId = this.getInterfaceId(interfaceId, pjp);
                    logger.error(interfaceId + ".error", e);
                }
            }
            throw e;
        }

        // 打印结果
        try {
            if (logger.isLogEnabled(logLevel)) {
                String resultStr = this.toString(resultAutoLog, result);
                interfaceId = this.getInterfaceId(interfaceId, pjp);
                logger.log(logLevel, "{}.result: {}", interfaceId, resultStr);
            }
        } catch (Exception e) {
            logger.error("AutoLog error", e);
        }

        // 返回结果
        return result;
    }

    /**
     * 将对象（参数或结果）转换为字符串
     * @param autoLog 日志标记
     * @param obj 对象
     * @return 字符串
     */
    private String toString(AutoLog autoLog, Object obj) {
        Mode mode = autoLog.mode();
        Serializer serializer = serializerFactory.getSerializer(mode);
        return serializer.serialize(obj);
    }

    /**
     * 获取接口ID
     * @param pjp pjp
     * @return 接口ID
     */
    private String getInterfaceId(String interfaceId, ProceedingJoinPoint pjp) {
        if (interfaceId != null) {
            return interfaceId;
        }
        String simpleName = pjp.getTarget().getClass().getSimpleName();
        String methodName = pjp.getSignature().getName();
        return simpleName + "." + methodName;
    }

    /**
     * 判断异常对象是不是配置的业务异常
     * @param e 异常对象
     * @return 是业务异常返回true，否则返回false
     */
    private boolean isServiceException(Throwable e) {
        if (serviceExceptionClasses == null || serviceExceptionClasses.isEmpty()) {
            return false;
        }
        for (Class<? extends RuntimeException> serviceExceptionClass : serviceExceptionClasses) {
            if (serviceExceptionClass.isInstance(e)) {
                return true;
            }
        }
        return false;
    }
}
